﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BobcatUtility;
using System.Runtime.Remoting.Contexts;

namespace CNShapes
{
    /// <summary>
    /// Holds a number of Pages, and represents one drawing session which may 
    /// consist of a number of different drawings.
    /// 
    /// Pages and ActivePage
    /// --------------------
    /// The session holds a collection of pages (Page objects).
    /// One of the pages is the active-page. This is the one that you are currently
    /// drawing on.
    /// 
    /// Singleton
    /// ---------
    /// This class is a singleton, as the whiteboard app hass just one session
    /// object which holds the current state of the set of drawings.
    /// </summary>
    [Synchronization]
    public class Session : ContextBoundObject
    {
        #region Events

        /// <summary>
        /// Data raised with events from this class.
        /// </summary>
        public class Args : EventArgs
        {
            public Page Page { get; set; }
            public Shape Shape { get; set; }
        }

        /// <summary>
        /// Event raised when the active page has changed.
        /// </summary>
        public event EventHandler<Args> ActivePageChangedEvent;

        /// <summary>
        /// Event raised when the collection of pages has changed. (Pages
        /// have been added or removed.)
        /// </summary>
        public event EventHandler<Args> PageCollectionChangedEvent;

        /// <summary>
        /// Event raised when a shape on one of the pages we're managing has
        /// finished being edited.
        /// </summary>
        public event EventHandler<Args> ShapeEditCompletedEvent;

        #endregion

        #region Public properties

        /// <summary>
        /// Property to get and set the session's ID.
        /// </summary>
        public Guid ID
        {
            get { return m_ID; }
        }

        /// <summary>
        /// Property to get the list of pages for this session.
        /// </summary>
        public IList<Page> Pages
        {
            get 
            {
                IList<Page> results = new List<Page>();
                foreach (KeyValuePair<Guid, Page> pair in m_mapPageIDsToPages)
                {
                    results.Add(pair.Value);
                }
                return results;
            }
        }

        /// <summary>
        /// Property to get or set the active page for the session.
        /// </summary>
        public Page ActivePage
        {
            get 
            { 
                return m_ActivePage; 
            }
            set
            {
                // We check that the page passed in is one that we are managing...
                if (value != null)
                {
                    bool bGotPage = m_mapPageIDsToPages.ContainsKey(value.ID);
                    if (bGotPage == false)
                    {
                        throw new Exception("Session does not contain the page passed to ActivePage.");
                    }
                }
                m_ActivePage = value;
                Utility.CrossThreadInvoke(ActivePageChangedEvent, this, new Args());
            }
        }

        /// <summary>
        /// Property to get or set the ID of the active page. If you set the property, this
        /// selects the page with the ID passed in. It does not change the ID of the current
        /// active page.
        /// </summary>
        public Guid ActivePageID
        {
            get
            {
                return m_ActivePage.ID;
            }
            set
            {
                bool bGotPage = m_mapPageIDsToPages.ContainsKey(value);
                if (bGotPage == false)
                {
                    throw new Exception("Page with ID=" + value.ToString() + " is not part of the current session.");
                }
                ActivePage = m_mapPageIDsToPages[value];
            }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Singleton's getInstance method.
        /// </summary>
        public static Session getInstance()
        {
            if (m_Instance == null)
            {
                m_Instance = new Session();
            }
            return m_Instance;
        }

        /// <summary>
        /// Clears the existing session and creates a new one.
        /// </summary>
        public void newSession()
        {
            newSession(Guid.NewGuid());
        }

        /// <summary>
        /// Clears the existing session and creates a new one.
        /// </summary>
        public void newSession(Guid sessionID)
        {
            // We clear the existing session...
            m_mapPageIDsToPages.Clear();
            Utility.CrossThreadInvoke(PageCollectionChangedEvent, this, new Args());
            ActivePage = null;

            // We set the ID...
            m_ID = sessionID;
        }

        /// <summary>
        /// Adds a page to the session and sets it as the active page.
        /// </summary>
        public Page addPage()
        {
            Page page = new Page();
            setupNewPage(page);
            return page;
        }

        /// <summary>
        /// Adds a page with the passed in page ID and sets it as the active page.
        /// </summary>
        public Page addPage(Guid pageID)
        {
            Page page = new Page(pageID);
            setupNewPage(page);
            return page;
        }

        /// <summary>
        /// Returns the page with the ID passed in.
        /// Throws an exception if we do not have the page in the session.
        /// </summary>
        public Page getPage(Guid pageID)
        {
            if (m_mapPageIDsToPages.ContainsKey(pageID) == false)
            {
                throw new Exception("Session does not contain page with ID=" + pageID.ToString());
            }
            return m_mapPageIDsToPages[pageID];
        }

        #endregion

        #region Private functions

        /// <summary>
        /// Private constructor (for singleton).
        /// </summary>
        private Session()
        {
            m_ID = Guid.NewGuid();
        }

        /// <summary>
        /// We set up a new page, adding it to our session, observing its events etc.
        /// </summary>
        private void setupNewPage(Page page)
        {
            // We observe shape-edit-completed events (so that we can pass
            // them to our observers, such as the networking client)...
            page.ShapeEditCompletedEvent += onShapeEditCompleted;

            // We add it to our collection, and raise an event...
            m_mapPageIDsToPages[page.ID] = page;
            Utility.CrossThreadInvoke(PageCollectionChangedEvent, this, new Args());

            // and we set it as the active page...
            ActivePage = page;
        }

        /// <summary>
        /// Called when a shape has been edited in one of the pages in the
        /// session.
        /// </summary>
        void onShapeEditCompleted(object sender, Page.Args e)
        {
            // We raise an event to our observers to let them know that the
            // shape has been edited, and which page it is on...
            Page page = (Page)sender;
            Args args = new Args{Page = page, Shape = e.Shape};
            Utility.CrossThreadInvoke(ShapeEditCompletedEvent, this, args);
        }

        #endregion

        #region Private data

        // Static singleton instance...
        private static Session m_Instance = null;

        // Unique ID of the session...
        private Guid m_ID;

        // The collection of pages in this session...
        private IDictionary<Guid, Page> m_mapPageIDsToPages = new Dictionary<Guid, Page>();

        // The active page...
        private Page m_ActivePage = null;

        #endregion
    }
}
